Redis中数据结构和编码详细图解(应用场景及优缺点)

专业术语

sds:simple dynamic string 简单动态字符串,redis自己开发的一个字符串的抽象类型

embstr:embedded sds string embstr编码的SDS,与SDS区别在于内存仅需要申请一次,而SDS需要申请两次。适用于短字符串,优点是效率高

Redis 对象结构

Redis 五种对象类型

redis对象数据结构如图所示:
在这里插入图片描述
在这里插入图片描述

每一个redis对象都用一个key进行存储,在redis对象中有对象类型和编码,对象类型和编码的对应关系如下:
在这里插入图片描述

string 字符串类型

应用场景:
5中类型中最简单、常用用的类型,扩展性是非常高得

  1. key是string,value可以是json,常规key-value缓存
  2. 可以用于一些计数器,比如文章点赞数

编码选择:
REDIS_ENCODING_INT:如果value是long类型的整数,则使用此编码
REDIS_ENCODING_RAW:专门保存长字符串的编码,如果value长度>=40(redis-3.3),则使用的是raw编码进行存储。

  • 不足:raw需要调用两次的内存分配函数来分别创建redisObjectsdshdr

REDIS_ENCODING_EMBSTR:专门保存短字符串的编码,如果value长度<40,则使用embstr编码,属于紧凑型的数据结构

  • 优点只需要一次内存分配,数据比较小的时候使用的是这种编码,数据更紧凑,效率更高
  • 缺点:没有提供修改的函数,所以是只读的,如果要对此编码数据进行修改,会变成raw再执行修改,然后结束
底层数据结构:
string的int编码:

在这里插入图片描述
在ptr指针中直接指向long类型整数

string的raw编码

在这里插入图片描述
ptr指向一个redis自定义的string类型->sdshdr,然后在buf缓冲中指向一个字符数组

string的embstr编码

在这里插入图片描述
于raw类似,只不过在所有属性都和RedisObject在一块连续的内存空间上,所以它只需要进行一次的内存申请,也代表着它很快!

string 相关命令
命令行含义
set key value赋值key的值为value
get key获取key的value值
del key删除key
expire key seconds设置key在seconds秒后过期
setex key seconds value赋值key的值为value,在seconds秒后过期
ttl key查看key还有多久过期
setnx key value如果key不存在,才新增key和value
strlen key计算指定key的值的长度
incr key加1
incrby key numbers指定增加值,numbers可以是负值
mset key1 value1 key2 value2 …批量添加
mget key1 key2 key3 …批量获取

list 列表类型

应用场景:

  1. 各种列表, twitter的关注列表、粉丝列表等,最新消息排行
  2. 消息队列,可以利用Lists的PUSH操作,将任务存在Lists中,然后工作线程再用POP操作将任务取出执行。

编码选择:
当列表对象同时满足以下两个条件时,列表对象使用ziplist进行存储,否则使用linkedlist存储

  1. 列表对象保存的所有字符串元素的长度小于64字节
  2. 列表对象保存的元素数量小于512个
list的zpilist编码

在这里插入图片描述
ptr指向一个压缩列表,所有的压缩列表都类似数据结构

list的linkedlist编码

在这里插入图片描述
双向无循环的链表,无循环代表头和尾都指向NULL,不会形成一个循环,优点和java的链表一样,插入快但索引慢

list 相关命令
命令行含义
lpush key value1 value2左侧插入value
rpush key value1 value2右侧插入value
lpop key左侧弹出value
rpop key右侧弹出value
llen key查看key的长度
lindex key index查看列表中某个index对应的value值
setnx key value如果key不存在,才新增key和value
lrange key startIndex endIndex查看指定元素,下标从0开始,-1为倒数第一个
ltrim key startIndex endIndex让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除,下标同上

set 集合类型

应用场景:

  1. 增加的时候可以保证去重,比如文章中用户每次点赞的时候不允许重复

编码选择:
同时符合以下两种情况时,使用的是intset编码,否则采用hashtable编码

  1. 集合对象保存的所有元素都是整数值
  2. 集合对象保存的所有元素<=512个
set的intset编码(intset底层为整形集合实现)

在这里插入图片描述
intset编码与raw类似,只不过将字符数组换成了整形数组

set的hashtable编码(底层是字典,每个键都是字符串对象。字典对应的值为NULL)

在这里插入图片描述
set集合类型下使用了hashtable,那么它指向的value保存什么呢?
看图中可以看出,所有的数据都指向了NULL

set 相关命令
命令行含义
sadd key value1 value2添加元素到集合中
smembers key查看集合中的所有元素
sismember key value查看value是否在集合中
scard key查询集合的长度
spop key取出集合中的一个元素
del key删除集合

zset 有序集合类型

应用场景:
一些需要对存放的数据进行排序,比如正序、倒序;不允许重复的成员,后面的数据会覆盖前面的数据

  1. 根据时间排序的新闻列表
  2. csdn的访问量排行榜

编码选择:
当zset满足以下两个条件的时候,使用ziplist,否则使用skiplist

  1. 保存的元素个数小于128个
  2. 保存的所有元素大小都小于64字节
zset的ziplist编码

在这里插入图片描述
一样使用ziplist压缩列表,在中间数据的部分保存的时key-value,连续存储的结构

zset的skiplist编码
typedef struct zset{
     // 跳表
     zskiplist *zsl;
     // 字典
     dict *dice;
} zset;

在这里插入图片描述
使用字典+跳表的方式,将字典和跳表两种数据结构组合
其中跳表通过在每个节点中(基于层和跨度等)维持多个指向其它节点的指针来实现快速访问
更多参考:Redis 源码分析(七) :skiplist

zset有序集合单独使用字典或跳表中的一种数据结构就可以实现,那么为什么要使用两者的组合呢?
  • 字典的优势在于如果要查找成员的分值,时间复杂度是O(1),但是字典是以无序的方式保存集合元素,所以每次范围操作的时候需要进行排序
  • 跳表的优势在于如果要执行范围操作,不需要进行排序,可以直接获取,但是跳表如果是进行查找操作,则时间复杂度为O(logN)
  • 因此Redis使用两种数据结构的组合,扬长避短的方式来共同实现有序集合
zset有序集合使用两种数据结构会不会造成数据冗余?

不会,两种数据结构会通过指针来共享相同数据元素的成员和分值,所以不会产生重复的成员和分值而造成内存的浪费。

zset 相关命令
命令行含义
zadd key value1 score1 value2 score2添加元素到有序集合中
zscore key value查看key的score值,输出score>=负无穷,score<=正无穷的所有元素
zrange key 0 -1正序输出
zrangebyscore key -inf +inf正序输出
zrevrange key 0 -1倒序输出
zcard key查看key中的元素个数
zrangebyscore key indexStart endStart获得key中score>=indexStart 且score<=endStart的元素,正序排列
zrevrangebyscore key indexStart endStart同上,倒序排列
zrem key value删除key中的元素value

hash 字典类型

应用场景:

  1. 存放结构化数据,比如user对象,修改时不需要反序列化可以直接修改

编码选择:
同时满足以下两种情况,则是zpilist,否则就是hashtable
3. hash里面的键值对中,key和value的长度全都小于46个字节
4. hash里面的键值对中,个数小于512个

hash的ziplist编码

在这里插入图片描述
zpilist压缩链表,数据部分存储的是一个对象的多个属性名和属性值,修改就是直接对所在数据位置修改,而不需要进行反序列化,因为它们是结构化存储,而不是像json一样当作一个string来存储

hash的hashtable编码

在这里插入图片描述
使用字典的方式存储属性名和属性值,修改也是直接对数据进行修改,不需要反序列化操作

hash 相关命令
命令行含义
hset key name value添加属性元素name和value到key中
hget key name查看key的name值
hmset key name1 value1 name2 value2批量添加key的属性元素
hmget key name1 name2批量获取key的属性元素
hlen key获得key的属性元素个数
hgetall key查询key中的所有元素

思考

为什么redis要自己开发一个字符串类型?

主要有如下几种原因:

  1. C中字符串中没有长度数据,每次都需要进行计算,使用一个元素去遍历,直到\0结束符号,复杂度为O(n)
    而在SDS中的复杂度是O(1),更快
  2. 缓冲区溢出:C字符串是存在缓存冲溢出的情况,前提是在增加元素之前没有进行足够的内存分配
    而SDS动态的执行空间的扩展,API会自动的进行空间的扩展,
  3. 在C字符串中,内存的重新分配是很耗费性能的
    • 空间预分配:
      • 如果对SDS进行修改后,SDS的长度<1M,那么这个时候,预分配的len=free
      • 如果对SDS进行修改后,SDS的长度>=1M,只会按照1M去预分配
    • 惰性释放:
      • 在删除字符串的时候,不及时释放,还继续保留空间,下次使用的时候就不需要再进行申请了
  4. C字符串中字符串是以\0结尾的,并且字符串不能包含空字符串,因此C字符串使用范围较小
    而在SDS中,使用len来判断数据长度,可以保存任意的数据。

总结

文章以图解的方式介绍了Redis中的五种数据类型和8中数据编码,并对其应用场景进行分析举例,相信大家看完对Redis的底层有了一定的了解

8种编码encoding对比总结:

数据编码描述优点缺点
REDIS_ENCODING_INTlong类型的整数占用内存少、速度快仅仅针对long类型整数
REDIS_ENCODING_EMBSTRembstr编码的简单动态字符串一次内存申请与释放不适用于长的字符串
REDIS_ENCODING_RAW简单动态字符串适用于长的字符串存储相对于embstr,需要两次内存申请与释放
REDIS_ENCODING_HT字典读写时间复杂度O(1)占用内存较多
REDIS_ENCODING_LINKEDLIST双向链表元素的个数较多时,访问元素的时间比压缩列表更快每个节点都维护了前置指针、后置指针,占用更多内存且内存不连续容易产生内存碎片
REDIS_ENCODING_ZIPLIST压缩列表空间连续,占用内存少,速度快元素数量大时,访问元素的时间较长
REDIS_ENCODING_INTSET整数集合占用内存远小于hashtable仅仅存整数
REDIS_ENCODING_SKIPLIST跳表+字典不满足ziplist可以使用skiplist来作为内部实现
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我思知我在

原创不易,多多一键三连

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值